package com.xjrsoft.common.utils;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Entity;
import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xjrsoft.common.annotation.Trans;
import com.xjrsoft.common.constant.GlobalConstant;
import com.xjrsoft.common.model.generator.ComponentConfig;
import com.xjrsoft.module.generator.constant.ComponentTypeConstant;
import com.xjrsoft.module.magicapi.service.IMagicApiService;
import com.xjrsoft.module.organization.entity.Department;
import com.xjrsoft.module.organization.entity.User;
import com.xjrsoft.module.system.entity.Area;
import com.xjrsoft.module.system.entity.DictionaryDetail;
import com.xjrsoft.module.system.service.IAreaService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.ssssssss.magicapi.modules.db.model.PageResult;

import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public final class ExcelUtil {

    public static final String EXPORT_REQUIRED_SUFFIX = "(*)";

    public static final String EXPORT_REQUIRED_HIGHLIGHT_STRING = "*";

    private static final RedisUtil redisUtil;

    private static final IMagicApiService magicApiService;

    private static final IAreaService areaService;

    static {
        redisUtil = SpringUtil.getBean(RedisUtil.class);
        magicApiService = SpringUtil.getBean(IMagicApiService.class);
        areaService = SpringUtil.getBean(IAreaService.class);
    }

    private ExcelUtil(){}

    public static ByteArrayOutputStream renderExportRequiredHead(ByteArrayOutputStream bot) {
        ByteArrayOutputStream resultBot = new ByteArrayOutputStream();
        try {
            XSSFWorkbook hssfWorkbook = new XSSFWorkbook(IoUtil.toStream(bot));
            renderExportRequiredHead(hssfWorkbook);
            hssfWorkbook.write(resultBot);
        } catch (Exception e) {
            log.error("渲染导出必填列头失败！",e );
            return bot;
        }
        return resultBot;
    }

    public static void renderExportRequiredHead(Workbook workbook) {
        Sheet sheetAt = workbook.getSheetAt(0);
        Row row = sheetAt.getRow(0);
        Font font = null;
        Font cellFont = null;
        int strIndex = StringUtils.indexOf(EXPORT_REQUIRED_SUFFIX, EXPORT_REQUIRED_HIGHLIGHT_STRING);
        for (int i = row.getFirstCellNum(); i <= row.getLastCellNum(); i++ ) {
            Cell cell = row.getCell(i);
            if (cell == null) {
                continue;
            }
            String value = cell.getStringCellValue();
            if (StrUtil.endWithIgnoreCase(value, EXPORT_REQUIRED_SUFFIX)) {
                if (font == null) {
                    CellStyle cellStyle = cell.getCellStyle();
                    cellFont = workbook.getFontAt(cellStyle.getFontIndex());
                    font = copyFont(workbook, cellFont);
                    font.setColor(IndexedColors.RED.getIndex());
                }

                // 找到高亮字符在字符串中的位置，然后设置上面定义的字体颜色
                int indexSuffix = StringUtils.lastIndexOf(value, EXPORT_REQUIRED_SUFFIX);
                cell.getRichStringCellValue().applyFont(indexSuffix + strIndex, indexSuffix  + strIndex + 1, font);
                cell.getRichStringCellValue().applyFont(indexSuffix  + strIndex + 1, value.length(), cellFont);
            }
        }
    }

    private static Font copyFont(Workbook workbook, Font font) {
        if (font == null) {
            return null;
        }
        Font newFont = workbook.createFont();
        newFont.setColor(font.getColor());
        newFont.setBold(font.getBold());
        newFont.setCharSet(font.getCharSet());
        newFont.setFontHeight(font.getFontHeight());
        newFont.setFontHeightInPoints(font.getFontHeightInPoints());
        newFont.setFontName(font.getFontName());
        newFont.setItalic(font.getItalic());
        newFont.setStrikeout(font.getStrikeout());
        newFont.setTypeOffset(font.getTypeOffset());
        newFont.setUnderline(font.getUnderline());
        return newFont;
    }

    /**
     * 转换导出数据（自定义表单）
     * @param dataList 转换的数据
     * @param componentConfigList 表单字段配置集合
     * @param isSave true转换为存到数据库数据（导入时调用），false为展示数据（导出时使用）
     */
    public static void transExcelData(List<Map<String, Object>> dataList, List<ComponentConfig> componentConfigList, boolean isSave) {
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }
        Map<String, List<DictionaryDetail>> fieldDicListMap = new HashMap<>(16);
        Map<String, List<Map<String, Object>>> apiDataListMap = new HashMap<>(16);

        List<Department> departmentList = null;
        List<User> userList = null;

        buildSourceData(fieldDicListMap, apiDataListMap, componentConfigList);

        for (ComponentConfig componentConfig : componentConfigList) {
            String fieldName = componentConfig.getBindField();
            if (StrUtil.isEmpty(fieldName)) {
                continue;
            }
            List<DictionaryDetail> detailList = null;
            List<Map<String, Object>> apiDataList = null;
            String srcName = isSave ? "label" : "value";
            String targetName = isSave ? "value" : "label";
            String componentType = componentConfig.getType();
            boolean isMulti = StrUtil.equalsIgnoreCase(componentType, ComponentTypeConstant.MULTIPLE_POPUP)
                    || StrUtil.equalsIgnoreCase(componentType, ComponentTypeConstant.CHECKBOX)
                    || (StrUtil.equalsIgnoreCase(componentType, ComponentTypeConstant.SELECT)
                        && MapUtils.getBoolean(componentConfig.getOptions(), "isMultiple", false));
            for (Map<String, Object> data : dataList) {
                Object value = data.get(fieldName);
                Object transValue = value;
                Map<String, Object> options = componentConfig.getOptions();
                if (StrUtil.equalsIgnoreCase(MapUtils.getString(options, "datasourceType"), "dic")) {
                    if (detailList == null) detailList = fieldDicListMap.get(fieldName);
                    List<String> dicValues = isMulti ? StrUtil.split(value.toString(), StringPool.COMMA) : ListUtil.toList(value.toString());
                    StringBuilder dicValue = new StringBuilder();
                    for (String savedValue : dicValues) {
                        for (DictionaryDetail detail : detailList) {
                            if (StrUtil.equalsIgnoreCase((isSave ? detail.getName() : detail.getValue()), String.valueOf(savedValue))) {
                                if (dicValue.length() > 0) dicValue.append(StringPool.COMMA);
                                dicValue.append(isSave ? detail.getValue() : detail.getName());
                            }
                        }
                    }
                    if (dicValue.length() > 0) transValue = dicValue.toString();
                } else if (StrUtil.equalsIgnoreCase(componentType, ComponentTypeConstant.CASCADE)) {
                    if (apiDataList == null) apiDataList = apiDataListMap.get(fieldName);
                    List<String> cascadeValues = StrUtil.split(value.toString(), StringPool.COMMA);
                    StringBuilder cascadeValue = new StringBuilder();
                    for (String savedValue : cascadeValues) {
                        for (Map<String, Object> item : apiDataList) {
                            if (StrUtil.equalsIgnoreCase(savedValue, MapUtils.getString(item, srcName))) {
                                if (cascadeValue.length() > 0) cascadeValue.append(StringPool.COMMA);
                                cascadeValue.append(MapUtils.getString(item, targetName));
                                apiDataList = (List<Map<String, Object>>) item.get("children");
                            }
                        }
                    }
                    if (cascadeValue.length() > 0) transValue = cascadeValue.toString();
                } else if (StrUtil.equalsIgnoreCase(MapUtils.getString(options, "datasourceType"), "api")) {
                    if (apiDataList == null) apiDataList = apiDataListMap.get(fieldName);
                    List<String> apiValues = isMulti ? StrUtil.split(value.toString(), StringPool.COMMA) : ListUtil.toList(value.toString());
                    StringBuilder apiValue = new StringBuilder();
                    for (String savedValue : apiValues) {
                        for (Map<String, Object> apiData : apiDataList) {
                            if (StrUtil.equalsIgnoreCase(MapUtils.getString(apiData, srcName), String.valueOf(savedValue))) {
                                if (apiValue.length() > 0) apiValue.append(StringPool.COMMA);
                                apiValue.append(apiData.get(targetName));
                            }
                        }
                    }
                    if (apiValue.length() > 0) transValue = apiValue.toString();
                } else if (StrUtil.equalsIgnoreCase(MapUtils.getString(options, "datasourceType"), "staticData")) {
                    List<Map<String, Object>> staticDataList = (List<Map<String, Object>>) options.get("staticOptions");
                    List<String> staticValues = isMulti ? StrUtil.split(value.toString(), StringPool.COMMA) : ListUtil.toList(value.toString());
                    StringBuilder staticValue = new StringBuilder();
                    for (String savedValue : staticValues) {
                        for (Map<String, Object> staticData : staticDataList) {
                            if (StrUtil.equalsIgnoreCase(MapUtils.getString(staticData, srcName), String.valueOf(savedValue))) {
                                if (staticValue.length() > 0) staticValue.append(StringPool.COMMA);
                                staticValue.append(staticData.get(targetName));
                            }
                        }
                    }
                    if (staticValue.length() > 0) transValue = staticValue.toString();
                } else if (StrUtil.equalsIgnoreCase(componentType, ComponentTypeConstant.AREA)) {
                    List<String> areaValues = StrUtil.split(value.toString(), StringPool.COMMA);
                    StringBuilder areaValue = new StringBuilder();
                    Long parentId = null;
                    for (String srcValue : areaValues) {
                        Area area = isSave ? areaService.getOne(Wrappers.lambdaQuery(Area.class).eq(Area::getName, srcValue)
                                .eq(parentId != null, Area::getParentId, parentId), false)
                                : areaService.getById(Long.valueOf(srcValue));
                        if (area == null) {
                            continue;
                        }
                        parentId = area.getId();
                        if (areaValue.length() > 0) {
                            areaValue.append(StringPool.COMMA);
                        }
                        areaValue.append(isSave ? area.getId() : area.getName());
                    }
                    if (areaValue.length() > 0) transValue = areaValue.toString();
                } else if ((StrUtil.equalsIgnoreCase(componentType, ComponentTypeConstant.INFO) && MapUtils.getInteger(options, "infoType" ) == 0)
                        || StrUtil.equalsIgnoreCase(componentType, ComponentTypeConstant.USER)) {
                    if (userList == null) userList = redisUtil.get(GlobalConstant.USER_CACHE_KEY, new TypeReference<List<User>>() {
                    });
                    for (User user : userList) {
                        String tagValue = isSave ? user.getName() : user.getId().toString();
                        if (StrUtil.equalsIgnoreCase(tagValue, String.valueOf(value))) {
                            transValue = isSave ? user.getId() : user.getName();
                            break;
                        }
                    }
                } else if ((StrUtil.equalsIgnoreCase(componentType, ComponentTypeConstant.INFO) && MapUtils.getInteger(options, "infoType" ) == 1)
                        || StrUtil.equalsIgnoreCase(componentType, ComponentTypeConstant.ORGANIZATION)) {
                    if (departmentList == null) departmentList = redisUtil.get(GlobalConstant.DEP_CACHE_KEY, new TypeReference<List<Department>>() {
                    });
                    for (Department department : departmentList) {
                        String tagValue = isSave ? department.getName() : department.getId().toString();
                        if (StrUtil.equalsIgnoreCase(tagValue, String.valueOf(value))) {
                            transValue = isSave ? department.getId() : department.getName();
                            break;
                        }
                    }
                }
                data.put(fieldName, transValue);
            }
        }
    }

    /**
     * 转换导出数据（代码生成器）
     * @param dataList 转换的数据
     * @param isSave true转换为存到数据库数据（导入时调用），false为展示数据（导出时使用）
     * @param <T>
     */
    public static <T> void transExcelData(List<T> dataList, boolean isSave) {
        if (CollectionUtils.isEmpty(dataList)) {
            return;
        }
        Map<String, List<DictionaryDetail>> fieldDicListMap = new HashMap<>(16);
        Map<String, List<Map<String, Object>>> apiDataListMap = new HashMap<>(16);

        List<Department> departmentList = null;
        List<User> userList = null;

        //默认取第一条数据 来获取是所有字段  以及 字段的 注解
        Object firstData = dataList.get(0);
        Field[] fields = ReflectUtil.getFields(firstData.getClass());
        buildSourceData(fieldDicListMap, apiDataListMap, fields);

        for (Field field : fields) {
            Trans annotation = field.getAnnotation(Trans.class);
            if (annotation == null) {
                continue;
            }
            field.setAccessible(true);
            String fieldName = field.getName();
            List<DictionaryDetail> detailList = null;
            List<Map<String, Object>> apiDataList = null;
            String srcName = isSave ? "label" : "value";
            String targetName = isSave ? "value" : "label";
            boolean isMulti = annotation.isMulti();
            for (T data : dataList) {
                try {
                    Object value = field.get(data);
                    if (ObjectUtils.isEmpty(value)) {
                        continue;
                    }
                    Object transValue = value;
                    switch (annotation.type()) {
                        case DIC:
                            if (detailList == null) detailList = fieldDicListMap.get(fieldName);
                            List<String> dicValues = isMulti ? StrUtil.split(value.toString(), StringPool.COMMA) : ListUtil.toList(value.toString());
                            StringBuilder dicValue = new StringBuilder();
                            for (String savedValue : dicValues) {
                                for (DictionaryDetail detail : detailList) {
                                    if (StrUtil.equalsIgnoreCase((isSave ? detail.getName() : detail.getValue()), String.valueOf(savedValue))) {
                                        if (dicValue.length() > 0) dicValue.append(StringPool.COMMA);
                                        dicValue.append(isSave ? detail.getValue() : detail.getName());
                                    }
                                }
                            }
                            if (dicValue.length() > 0) transValue = dicValue.toString();
                            break;
                        case API:
                            if (apiDataList == null) apiDataList = apiDataListMap.get(fieldName);
                            List<String> apiValues = isMulti ? StrUtil.split(value.toString(), StringPool.COMMA) : ListUtil.toList(value.toString());
                            StringBuilder apiValue = new StringBuilder();
                            for (String savedValue : apiValues) {
                                for (Map<String, Object> apiData : apiDataList) {
                                    if (StrUtil.equalsIgnoreCase(MapUtils.getString(apiData, srcName), String.valueOf(savedValue))) {
                                        if (apiValue.length() > 0) apiValue.append(StringPool.COMMA);
                                        apiValue.append(apiData.get(targetName));
                                    }
                                }
                            }
                            if (apiValue.length() > 0) transValue = apiValue.toString();
                            break;
                        case CASCADE:
                            if (apiDataList == null) apiDataList = apiDataListMap.get(fieldName);
                            List<String> cascadeValues = StrUtil.split(value.toString(), StringPool.COMMA);
                            StringBuilder cascadeValue = new StringBuilder();
                            for (String savedValue : cascadeValues) {
                                for (Map<String, Object> item : apiDataList) {
                                    if (StrUtil.equalsIgnoreCase(savedValue, MapUtils.getString(item, srcName))) {
                                        if (cascadeValue.length() > 0) cascadeValue.append(StringPool.COMMA);
                                        cascadeValue.append(MapUtils.getString(item, targetName));
                                        apiDataList = (List<Map<String, Object>>) item.get("children");
                                    }
                                }
                            }
                            if (cascadeValue.length() > 0) transValue = cascadeValue.toString();
                            break;
                        case AREA:
                            List<String> areaValues = StrUtil.split(value.toString(), StringPool.COMMA);
                            StringBuilder areaValue = new StringBuilder();
                            Long parentId = null;
                            for (String srcValue : areaValues) {
                                Area area = isSave ? areaService.getOne(Wrappers.lambdaQuery(Area.class).eq(Area::getName, srcValue)
                                        .eq(parentId != null, Area::getParentId, parentId), false)
                                        : areaService.getById(Long.valueOf(srcValue));
                                if (area == null) {
                                    continue;
                                }
                                parentId = area.getId();
                                if (areaValue.length() > 0) {
                                    areaValue.append(StringPool.COMMA);
                                }
                                areaValue.append(isSave ? area.getId() : area.getName());
                            }
                            if (areaValue.length() > 0) transValue = areaValue.toString();
                            break;
                        case DEPT:
                            if (departmentList == null) departmentList = redisUtil.get(GlobalConstant.DEP_CACHE_KEY, new TypeReference<List<Department>>() {
                            });
                            for (Department department : departmentList) {
                                String tagValue = isSave ? department.getName() : department.getId().toString();
                                if (StrUtil.equalsIgnoreCase(tagValue, String.valueOf(value))) {
                                    transValue = isSave ? department.getId().toString() : department.getName();
                                    break;
                                }
                            }
                            break;
                        case USER:
                            if (userList == null) userList = redisUtil.get(GlobalConstant.USER_CACHE_KEY, new TypeReference<List<User>>() {
                            });
                            for (User user : userList) {
                                String tagValue = isSave ? user.getName() : user.getId().toString();
                                if (StrUtil.equalsIgnoreCase(tagValue, String.valueOf(value))) {
                                    transValue = isSave ? user.getId().toString() : user.getName();
                                    break;
                                }
                            }
                            break;
                    }
                    field.set(data, transValue);
                } catch (IllegalAccessException e) {
                    log.warn(field.getName() + annotation.type().getValue() + "数据转换异常！");
                }
            }
        }
    }

    /**
     * 加载源数据
     * @param fieldDicListMap 数据字典数据
     * @param apiDataListMap Magic-API数据
     * @param fields 字段
     */
    private static void buildSourceData(Map<String, List<DictionaryDetail>> fieldDicListMap, Map<String,
            List<Map<String, Object>>> apiDataListMap, Field[] fields) {
        List<DictionaryDetail> detailList = null;
        for (Field field : fields) {
            String fieldName = field.getName();
            Trans annotation = field.getAnnotation(Trans.class);
            if (annotation == null) {
                continue;
            }
            String id = annotation.id();
            switch (annotation.type()) {
                case DIC:
                    if (detailList == null) detailList = redisUtil.get(GlobalConstant.DIC_DETAIL_CACHE_KEY, new TypeReference<List<DictionaryDetail>>() {
                    });
                    List<DictionaryDetail> fieldDetailList = new ArrayList<>();
                    for (DictionaryDetail detail : detailList) {
                        if (Long.parseLong(id) == detail.getItemId()) {
                            fieldDetailList.add(detail);
                        }
                    }
                    fieldDicListMap.put(fieldName, fieldDetailList);
                    break;
                case API:
                case CASCADE:
                    Object fieldApiDataList = magicApiService.executeApi(id);
                    if (fieldApiDataList instanceof PageResult) {
                        apiDataListMap.put(fieldName, (List<Map<String, Object>>) ((PageResult) fieldApiDataList).getList());
                    } else if (fieldApiDataList instanceof List) {
                        apiDataListMap.put(fieldName, (List<Map<String, Object>>) fieldApiDataList);
                    }
                    break;
            }
        }
    }

    /**
     * 加载源数据
     * @param fieldDicListMap 数据字典数据
     * @param apiDataListMap Magic-API数据
     * @param componentConfigList 字段
     */
    private static void buildSourceData(Map<String, List<DictionaryDetail>> fieldDicListMap, Map<String,
            List<Map<String, Object>>> apiDataListMap, List<ComponentConfig> componentConfigList) {
        List<DictionaryDetail> detailList = null;
        for (ComponentConfig componentConfig : componentConfigList) {
            String fieldName = componentConfig.getBindField();
            if (StrUtil.isEmpty(fieldName)) {
                continue;
            }
            Map<String, Object> options = componentConfig.getOptions();
            if (StrUtil.equalsIgnoreCase(MapUtils.getString(options, "datasourceType"), "dic")) {
                String itemId = MapUtils.getString(options, "itemId");
                if (detailList == null)
                    detailList = redisUtil.get(GlobalConstant.DIC_DETAIL_CACHE_KEY, new TypeReference<List<DictionaryDetail>>() {
                    });
                List<DictionaryDetail> fieldDetailList = new ArrayList<>();
                for (DictionaryDetail detail : detailList) {
                    if (Long.parseLong(itemId) == detail.getItemId()) {
                        fieldDetailList.add(detail);
                    }
                }
                fieldDicListMap.put(fieldName, fieldDetailList);
            } else if (StrUtil.equalsIgnoreCase(componentConfig.getType(), ComponentTypeConstant.CASCADE)
                    || StrUtil.equalsIgnoreCase(MapUtils.getString(options, "datasourceType"), "api")) {
                Map<String, Object> apiConfig = MapUtils.getMap(options, "apiConfig");
                String apiId = MapUtils.getString(apiConfig, "apiId");
                Object fieldApiDataList = magicApiService.executeApi(apiId);
                if (fieldApiDataList instanceof PageResult) {
                    apiDataListMap.put(fieldName, (List<Map<String, Object>>) ((PageResult) fieldApiDataList).getList());
                } else if (fieldApiDataList instanceof List) {
                    apiDataListMap.put(fieldName, (List<Map<String, Object>>) fieldApiDataList);
                }
            }
        }
    }
}
